package run;

import java.awt.Container;
import java.util.LinkedList;
import java.util.List;

import processing.core.PApplet;
import processing.core.PVector;

public class LineCell {
	
	int generation;
	PVector loc;
	PVector dir;
	PApplet p;
	public boolean dead = false;
	SandPainter paint;
	List<PVector> pastLocs;
	List<DirChanger> dirChangers = new LinkedList();
	
	List<DistRule> distRules = new LinkedList();
	
	private List<CollideRule> collideRules = new LinkedList();

	int r,g,b;
	
	public LineCell(PVector pVector, PVector dir, PApplet p, int gen) {
		loc = pVector;
		this.dir = dir;
		dir.normalize();
		this.generation = gen;
		this.p = p;
		paint = new SandPainter(p);
		pastLocs = new LinkedList<PVector>();
		
		
		collideRules.add(new RandomPastPerpendicularSpawnRule());
		collideRules.add(new InflectionDirectionRule());
		distRules.add(new RepulseRule());
		
		r = (int) p.random(100,250);
		g = (int) p.random(40,150);
		b = (int) p.random(20,200);
	}
	
	public int getAge() {
		return pastLocs.size();
	}
	
	// normalizes a value to a new domain
	private float castToRange(float min, float max, float omin, float omax, float value) {
		return ( value / (omax - omin) ) * (max - min);
	}
	
	public void move( Env e) {
		
		if (dead) return;
		
	    addLocalHist(loc);
	    
	    float x = loc.x;
	    float y = loc.y;
	    
	    linearStrokeDensity(loc, pastLocs.get(0));
	    
	    // draw the hazy line
	    float hw = 3;
	    for (int i = 0; i < 5; i++) {
	      float hazyX = x+random(-hw, hw);
	      float hazyY = y+random(-hw, hw);
	      PVector hazy = new PVector(hazyX, hazyY, 0);
	      // dist can be 0 to .43*5=2.5... mult by 30
	      float strokeAlph = 10 + 80 / (loc.dist(hazy) * loc.dist(hazy));
	      p.stroke(r,g,b, strokeAlph);
	      p.point(hazyX, hazyY);
	    }
	    
	    //paint.render( loc.x+20, loc.y+20, loc.x, loc.y, p);
	    
	    // Direction Changers?
	    for (DirChanger d : dirChangers) {
	    	d.changeDir(dir);
	    }
	    
	    // Distance-related rules
	    PVector distSum = new PVector(0,0,0);
	    for (LineCell c : e.cells) {
		    for (DistRule d : distRules) {
		    	distSum.add(d.execRule(this, c, e, p));
		    }
	    }
	    distSum.normalize();
	    distSum.mult(0.01f);
	    dir.add(distSum);
	    
	    //TODO think of a good way to threshold it... we want more interesting shit happening
	    // in the emptier spaces.
	    dir.normalize();
	    loc.add(dir);
	    
	    // what's a fast way to detect collisions?
	    // perhaps... whenever we change direction, we simply place a
	    // point down. ... ugh
	    if (loc.x < 0 || loc.y<0 || loc.x>900 || loc.y > 500) {
	    	dead=true;
	    	loc = new PVector(0,0,0);
	    }

	    // COLLISION
	    LineCell collided = e.isCollide(this);
	    if (collided != null) {
	    	dead=true;
	    	// pick random past locs to spawn!
	    	for (CollideRule r : collideRules) {
	    		r.executeRule(this, collided, e, p);
	    	}
	    }
	  
	    e.addHist(this, this.loc);
	}

	// we want strokedensity a function of pos, pos-original, 
	//     controlling parameters of alpha and spread
	private void linearStrokeDensity(PVector curPos, PVector origPos) {
	    
	    float x = curPos.x;
	    float y = curPos.y;
	    
	    // TODO have some of these be random variables...
	    //TODO some start weak and end strong, others start strong and end weak...
	    // perhaps tie it to their proximity to other lines!!!
	    
	    float max_dens = 30;
	    float min_dens = 5;
	    float reduct_rate = 0.5F;
	    int point_count = 5;
	    float dens = larger(max_dens - reduct_rate * (curPos.dist(origPos)), min_dens);
	    
	    // let the width vary proportionately to the density. coherence.
	    float max_width = 2f;
	    float width = castToRange(0F, max_width, min_dens, max_dens, dens);
	    for (int i = 0; i < point_count; i++) {

	    	// ALSO depend upon generation(a bit) & dist from nearest neighbors...
	    	// alpha smaller the greater the width
	    	p.stroke(r,g,b, 60 / ( width + 0.25F*generation ));
		    p.point(x+random(-width,width),y+random(-width,width));
	    }
	}
	
	private float larger(float x, float y) {
		if (x > y)
			return x;
		else return y;
	}

	private void addLocalHist(PVector loc2) {
		pastLocs.add(new PVector(loc2.x, loc2.y, 0));
	}

	private float random(float x, float y) {
		return p.random(x,y);
	}

	public void addDirChanger(DirChanger d) {
		dirChangers.add(d);
	}
	
	
}
